package rad.framework.hibernate;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.cfg.DefaultComponentSafeNamingStrategy;

/**
 * @author Chunyun Zhao
 * @author boudyacho
 */
public class NamingStrategy extends DefaultComponentSafeNamingStrategy {
	private static Log log = LogFactory.getLog(NamingStrategy.class);

	// @Override
	protected static String addUnderscores(String name) {
		return name.replace('.', '_');// .toLowerCase();
	}

	@Override
	public String collectionTableName(String ownerEntity,
			String ownerEntityTable, String associatedEntity,
			String associatedEntityTable, String propertyName) {
		return checkTableName(getAbbreviation(tableName(new StringBuilder(ownerEntityTable)
				.append("_").append(
						associatedEntityTable != null ? associatedEntityTable
								: addUnderscores(propertyName)).toString())));
		// return getAbbreviation(super.collectionTableName(ownerEntity,
		// ownerEntityTable, associatedEntity, associatedEntityTable,
		// propertyName));
	}

	@Override
	public String foreignKeyColumnName(String propertyName,
			String propertyEntityName, String propertyTableName,
			String referencedColumnName) {
		String header = propertyName != null ? addUnderscores(propertyName)
				: propertyTableName;
		if (header == null)
			throw new AssertionFailure("NamingStrategy not properly filled");
		return checkColumnName(getAbbreviation(columnName(header + "_" + referencedColumnName)));
		// return getAbbreviation(super.foreignKeyColumnName(propertyName,
		// propertyName, propertyTableName, referencedColumnName));
	}

	@Override
	public String logicalCollectionColumnName(String columnName,
			String propertyName, String referencedColumn) {
		return getAbbreviation(super.logicalCollectionColumnName(columnName,
				propertyName, referencedColumn));
	}

	@Override
	public String logicalCollectionTableName(String tableName,
			String ownerEntityTable, String associatedEntityTable,
			String propertyName) {
		return getAbbreviation(super.logicalCollectionTableName(tableName,
				ownerEntityTable, associatedEntityTable, propertyName));
	}

	@Override
	public String classToTableName(String className) {
		return checkTableName(getAbbreviation(super.classToTableName(className)));// + "S";
	}

	@Override
	public String columnName(String columnName) {
		return checkColumnName(getAbbreviation(super.columnName(columnName)));
	}

	@Override
	public String joinKeyColumnName(String joinedColumn, String joinedTable) {
		return checkColumnName(getAbbreviation(super.joinKeyColumnName(
				joinedColumn, joinedTable)));
	}

	protected String checkColumnName(String name) {
		// Limit is 26 characters for columns
		if (name.length() > 26) {
			throw new RuntimeException(
					"Column Name greater than 26 characters: " + name + " : "
							+ name.length());
		}
		checkForUncap(name);
		return name;
	}

	protected String checkTableName(String name) {
		// Limit is21 characters for tables
		if (name.length() > 21) {
			throw new RuntimeException(
					"Table Name greater than 21 characters: " + name + " : "
							+ name.length());
		}
		checkForUncap(name);
		return name;
	}

	protected void checkForUncap(String name) {
		char[] chars = name.toCharArray();
		for (char c : chars) {
			if (Character.isLowerCase(c)) {
				throw new RuntimeException("Uncapitalized name: " + name);
			}
		}
	}
	
	@Override
	public String tableName(String tableName) {
		return checkTableName(getAbbreviation(super.tableName(tableName)));
	}

	@Override
	public String logicalColumnName(String columnName, String propertyName) {
		return getAbbreviation(super
				.logicalColumnName(columnName, propertyName));
	}

	@Override
	public String propertyToColumnName(String propertyName) {
		return checkColumnName(getAbbreviation(addUnderscores(propertyName)));
		// return getAbbreviation(super.propertyToColumnName(propertyName));
	}

	protected String getAbbreviation(String name) {
		return getProperty(getAbbreviations(name));
	}

	protected String getAbbreviations(String name) {
		//Look for abbrv of entire word before parts
		String abbrv = getProperty(name);
		if (!abbrv.equals(name)) {
			return abbrv;
		}
		
		List parts = parseName(name);

		StringBuffer result = new StringBuffer();
		for (Iterator nameIter = parts.iterator(); nameIter.hasNext();) {
			String nameComponent = (String) nameIter.next();
			nameComponent = getProperty(nameComponent);
			result.append(nameComponent);
			if (nameIter.hasNext() && nameComponent.length() > 1) {
				result.append("_");
			}
		}

		return result.toString();
	}

	protected List parseName(String sourceName) {
		char sourceSeparator = '_';
		List parts = new ArrayList();
		StringBuffer currentWord = new StringBuffer();
		int length = sourceName.length();
		boolean lastIsLower = false;
		for (int index = 0; index < length; index++) {
			char curChar = sourceName.charAt(index);
			if (Character.isUpperCase(curChar)
					|| (!lastIsLower && Character.isDigit(curChar))
					|| curChar == sourceSeparator) {
				if (lastIsLower || curChar == sourceSeparator) {
					parts.add(currentWord.toString());
					currentWord = new StringBuffer();
				}
				lastIsLower = false;
			} else {
				if (!lastIsLower) {
					int currentWordLength = currentWord.length();
					if (currentWordLength > 1) {
						char lastChar = currentWord.charAt(--currentWordLength);
						currentWord.setLength(currentWordLength);
						parts.add(currentWord.toString());
						currentWord = new StringBuffer();
						currentWord.append(lastChar);
					}
				}
				lastIsLower = true;
			}
			if (curChar != sourceSeparator) {
				currentWord.append(curChar);
			}
		}
		parts.add(currentWord.toString());
		return parts;
	}

	protected String getProperty(String word) {
		String abbrv = abbrvProperties.getProperty(word);
		if (abbrv == null) {
			return word;
		} else {
			return abbrv;
		}
	}

	private static Properties abbrvProperties;
	static {
		try {
			abbrvProperties = new Properties();
			InputStream resourceAsStream = NamingStrategy.class
					.getResourceAsStream("/abbreviation.properties");
			abbrvProperties.load(resourceAsStream);
		} catch (Exception e) {
			log.error("Unable to load abbreviation properties, ignore it.", e);
		}
	}
}
